#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using IPSim.Data;
using IPSim.Util;

namespace IPSim.Core
{
    public interface IConnectable
    {
        void Connect(IConnectable device, byte num);
        void Disconnect(IConnectable device, byte num);
        TransportEthernetFrame FrameReceiver1 { get; }
        TransportEthernetFrame FrameReceiver2 { get; }
        TransportEthernetFrame FrameReceiver3 { get; }
        TransportEthernetFrame FrameReceiver4 { get; }
    }

    public delegate void TransportEthernetFrame(TransportEthernetFrame receiver, EthernetFrame frame);

    public class TransportEthernetDataEventArgs : EventArgs
    {
        private readonly IEthernetData m_ethernetData;
        private readonly EthernetFrame.EtherTypes m_etherType;

        /// <summary>
        /// 
        /// </summary>
        public TransportEthernetDataEventArgs(IEthernetData ethernetData, EthernetFrame.EtherTypes etherType)
        {
            m_ethernetData = ethernetData;
            m_etherType = etherType;
        }

        /// <summary>
        /// Data encapsulated in event argument
        /// </summary>
        public IEthernetData EthernetData
        {
            get { return m_ethernetData; }
        }
        public EthernetFrame.EtherTypes EtherType
        {
            get { return m_etherType; }
        }
    }

    /// <summary>
    /// Delegat za TransportEthernetData event!
    /// </summary>
    /// <param name="source"></param>
    /// <param name="e"></param>
    public delegate void TransportEthernetDataEventHandler(object source, TransportEthernetDataEventArgs e);

    /// <summary>
    /// Klasa koja predstavlja funkcionalnost Ethernet Interface-a.
    /// Salje i prima podatke sa Network segmenta.
    /// Poseduje funkcionalnost obradjivanja pristiglih frame-ova i prosledjivanja istih na vise nivoe.
    /// </summary>
    public class EthernetInterface
    {

        /// <summary>
        /// m_ethernetBuffer queue u sebi sadrzi Ethernet Frame-ove pristigle na ethernet interface. 
        /// Sadrzi samo frame-ove namenjene ovom interface-u.
        /// </summary>
        private Queue<EthernetFrame>        m_ethernetBuffer;
        private MacAddress                  m_macAddress;
        private Thread                      m_frameProccessorThread;
        private TransportEthernetFrame      m_frameReceiver;
        private TransportEthernetFrame      m_frameSender;
        private readonly static Type        declaringType = typeof(EthernetInterface);

        /// <summary>
        /// This event triggers all upper level interfaces (IP and ARP interfaces),
        /// and passes them IEthernetData.
        /// </summary>
        public event TransportEthernetDataEventHandler TransportEthernetData;

        /// <summary>
        /// Ova funkcija je pozvana od strane event handle-ra za event TransportFrame na Network Segmentu.
        /// Proverava da li je destination mac adresa frame-a odgovarajuca, 
        /// sto znaci da je ili ista kaoadresa ethernet interfejsa ili broadcast mac adresa.
        /// Ukoliko jeste, stavlja se u red. Kasnije ce je obraditi EthernetCore.
        /// </summary>
        /// <param name="source">referenca na objekat koji je poslao frame</param>
        /// <param name="e">U sebi sadrzi Ethernet Frame koji prenosimo</param>
        public void ReceiveFrame(TransportEthernetFrame receiver, EthernetFrame frame)
        {
            if ((frame.DestAddress.Address == m_macAddress.Address) || (frame.DestAddress.Address == MacAddress.macBroadcast))
            {
                lock (m_ethernetBuffer)
                {
                    m_ethernetBuffer.Enqueue(frame);
                    Monitor.PulseAll(m_ethernetBuffer);                    
                }
            }
        }
        /// <summary>
        /// Ova metoda dekapsulira ethernet frame, i trigeruje event na network layer-u sa podacima iz frame-a.        
        /// </summary>
        /// <param name="ethernetFrame"></param>
        private void ProccessFrame(EthernetFrame ethernetFrame)
        {
            Logger.log("[" + m_macAddress + "] Primljen je ethernet frame", LogLevel.INFO);
            this.TransportEthernetData(null, new TransportEthernetDataEventArgs(ethernetFrame.Data, ethernetFrame.EtherType));
        }

        /// <summary>
        /// Ovaj metod (aktivan - poseban thread) u beskonacnoj petlji proverava frameQueue 
        /// i iz njega uzima frame-ove i poziva metodu koja ih obradjuje.
        /// </summary>
        private void FrameProccessorMethod()
        {
            EthernetFrame tempFrame;
            while(true)
            {
                lock (m_ethernetBuffer)
                {
                    tempFrame = null;
                    while (m_ethernetBuffer.Count == 0) Monitor.Wait(m_ethernetBuffer);
                    tempFrame = m_ethernetBuffer.Dequeue();
                }
                ProccessFrame(tempFrame);
            }
        }
        /// <summary>
        /// Given ARP or IP packet (or any other Packet implementing IEthernetData) and destination Mac address, 
        /// this method creates Ethernet Frame and publish it to the LAN (Network Segment).
        /// </summary>
        /// <param name="packet"></param>
        /// <param name="destAddress"></param>
        public void ReceivePacketFromUpperLevel(IEthernetData packet, MacAddress destAddress)
        {            
            SendFrame(new EthernetFrame(packet, destAddress, m_macAddress, packet.EtherType));
        }


        /// <summary>
        /// Ethernet interface ovom metodom salje ethernet frame u etar, tj. na network segment, 
        /// pozivajuci metoduethernet segmenta koja trigeruje transportframe event.
        /// </summary>
        /// <param name="ethernetFrame"></param>
        private void SendFrame(EthernetFrame ethernetFrame)
        {
            Logger.log("[" + m_macAddress + "] Poslat je Ethernet Frame", LogLevel.INFO);
            if(m_frameSender!=null)
                m_frameSender(m_frameReceiver, ethernetFrame);
        }


        /// <summary>
        /// Constructor kojim dodeljujemo prazan queue, novu random mac adresu, i registrujemo ga na network segment
        /// </summary>
        /// <param name="networkSegment"></param>
        public EthernetInterface()
        {
            m_ethernetBuffer = new Queue<EthernetFrame>();
            m_macAddress = new MacAddress();
            m_frameReceiver = new TransportEthernetFrame(this.ReceiveFrame);
            m_frameProccessorThread = new Thread(new ThreadStart(FrameProccessorMethod));
            m_frameProccessorThread.Start();
            Util.Logger.log("Eth interface created: [" + m_macAddress + "]", LogLevel.INFO);            
        }


        /// <summary>
        /// Raise TransportEthernetData event. This event triggers all upper level interfaces (IP and ARP interfaces),
        /// and passes them IEthernetData.
        /// </summary>
        /// <param name="ethernetData">Data to pass</param>
        /// <param name="etherType">Type of passing data</param>
        public void OnTransportEthernetData(IEthernetData ethernetData, EthernetFrame.EtherTypes etherType)
        {
            TransportEthernetData(null, new TransportEthernetDataEventArgs(ethernetData, etherType));
        }


        /// <summary>
        /// Kills interface thread
        /// </summary>
        ~EthernetInterface()
        {
            Logger.log("[" + m_macAddress + "] Ethernet interface stoping", LogLevel.INFO);
            if (m_frameProccessorThread != null)
            {
                m_frameProccessorThread.Abort();
            }
        }

        /// <summary>
        /// Kills interface thread
        /// </summary>
        public void StopInterface()
        {
            Logger.log(" Ethernet interface stoping", LogLevel.INFO);
            if (m_frameProccessorThread != null)
            {
                m_frameProccessorThread.Abort();
            }
        }

        public MacAddress MacAddress
        {
            get
            {
                return m_macAddress;
            }
            set
            {
                m_macAddress = value;
            }
        }

        #region IConnectable Members

        public void Connect(IConnectable device, byte num)
        {
            m_frameSender = device.FrameReceiver1;            
        }

        public void Disconnect(IConnectable device, byte num)
        {
            m_frameSender = null;            
        }

        public TransportEthernetFrame FrameReceiver1
        {
            get { return m_frameReceiver; }
        }

        #endregion
    }
}
